#include "pch.h"
#include "targets.h"
#include "Texture2D.h"
#include "Helper.h"
#include "ErrorCodes.h"
#include "DXAdapter.h"

using namespace Platform;
using namespace concurrency;

using namespace DirectX;
using namespace D2D1;

using namespace Microsoft::WRL;
using namespace Windows::UI::Core;
using namespace Windows::Foundation;
using namespace Windows::Graphics::Display;
using namespace Windows::UI::Xaml::Controls;
using namespace Windows::UI::Xaml::Data;
using namespace Windows::UI::Xaml::Media::Imaging;

using namespace DXBase;
using namespace DXBase::D3D;

DXTargetSwapPanelOrWindow::DXTargetSwapPanelOrWindow()
{
	Init(nullptr);
}
DXTargetSwapPanelOrWindow::DXTargetSwapPanelOrWindow(SwapChainBackgroundPanel^ panel)
{
	if (!panel)
		throw ref new NullReferenceException();
	Init(panel);
}
void DXTargetSwapPanelOrWindow::Init(SwapChainBackgroundPanel^ panel)
{
	watcher.Target = this;
	this->panel = panel;
	dpi = Windows::Graphics::Display::DisplayProperties::LogicalDpi;
}
void DXTargetSwapPanelOrWindow::ValidateTarget()
{
	// check that size hasn't changed
	if (swapChain)
	{
		DXGI_SWAP_CHAIN_DESC1 desc;
		if(FAILED(swapChain->GetDesc1(&desc)))
		{
			// recreate everything
			ClearDXData();
			Context->CreateDeviceResources();
			return;
		}
		auto uSize = PixelSize;
		if (desc.Width != uSize.width || desc.Height != uSize.height)
		{
			Context->m_d3dContext->Flush();
			Context->m_d3dContext->OMSetRenderTargets(0, nullptr, nullptr);
			Context->m_d2dContext->SetTarget(nullptr);

			ntarget = nullptr;
			return;
		}
	}
}
void DXTargetSwapPanelOrWindow::Context::set(DXContext^ value)
{
	if (value == Context)
		return;
	if (value && Context && Context->Target == this)
		throw ExHelper::CreateException(ErrorCodes::TargetIsInUse);
	watcher.Context = value;
}
USize DXTargetSwapPanelOrWindow::PixelSize::get()
{
	auto w = Windows::UI::Core::CoreWindow::GetForCurrentThread();
	if (w)
		return DPI::GetUDDP(
			Windows::Graphics::Display::DisplayProperties::LogicalDpi, 
			Windows::Foundation::Size(w->Bounds.Width, w->Bounds.Height));
	return makeUSize(0, 0);
}
void DXTargetSwapPanelOrWindow::RenderFinished()
{
	if (!ntarget)
		return;

	// The application may optionally specify "dirty" or "scroll"
	// rects to improve efficiency in certain scenarios.
	DXGI_PRESENT_PARAMETERS parameters = {0};
	parameters.DirtyRectsCount = 0;
	parameters.pDirtyRects = nullptr;
	parameters.pScrollRect = nullptr;
	parameters.pScrollOffset = nullptr;
	
	// The first argument instructs DXGI to block until VSync, putting the application
	// to sleep until the next VSync. This ensures we don't waste any cycles rendering
	// frames that will never be displayed to the screen.
	HRESULT hr = swapChain->Present1(1, 0, &parameters);

	// Discard the contents of the render target.
	// This is a valid operation only when the existing contents will be entirely
	// overwritten. If dirty or scroll rects are used, this call should be removed.
	//Context->m_d3dContext->DiscardView(ntarget->m_renderTargetView.Get());

	// Discard the contents of the depth stencil.
	//Context->m_d3dContext->DiscardView(ntarget->m_depthStencilView.Get());

	// If the device was removed either by a disconnect or a driver upgrade, we 
	// must recreate all device resources.
	if (hr == DXGI_ERROR_DEVICE_REMOVED)
	{
		ClearDXData();
		Context->CreateDeviceResources();
	}
	else
	{
		HR2Ex(hr);
	}
}
void DXTargetSwapPanelOrWindow::LoadDXData()
{
	int countcall = 0;
start:;
	countcall++;

	if (ntarget)
		return;
	if (!Context)
		throw ExHelper::CreateException(ErrorCodes::ContextNotSet);

	auto window = Windows::UI::Core::CoreWindow::GetForCurrentThread();
	if (window == nullptr)
		return;
	auto usize = PixelSize;
	if(swapChain != nullptr)
	{
		// If the swap chain already exists, resize it.
        HRESULT hr = swapChain->ResizeBuffers(
            2, // Double-buffered swap chain.
			usize.width,
            usize.height,
            DXGI_FORMAT_B8G8R8A8_UNORM,
            0
            );

        if (hr == DXGI_ERROR_DEVICE_REMOVED && countcall == 1)
        {
            // If the device was removed for any reason, a new device and swapchain will need to be created.
			ClearDXData();
			Context->CreateDeviceResources();
            // Everything is set up now. Do not continue execution of this method.
            goto start;
        }
        else
        {
            HR2Ex(hr);
        }
	}
	else
	{
		// get scaling
		DXGI_SCALING mScaling = panel ? DXGI_SCALING_STRETCH : DXGI_SCALING_NONE;

		// Otherwise, create a new one using the same adapter as the existing Direct3D device.
		DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {0};
		swapChainDesc.Width = usize.width;
		swapChainDesc.Height = usize.height;
		swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; // This is the most common swap chain format.
		swapChainDesc.Stereo = false;
		swapChainDesc.SampleDesc.Count = 1; // Don't use multi-sampling.
		swapChainDesc.SampleDesc.Quality = 0;
		swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
		swapChainDesc.BufferCount = 2; // Use double-buffering to minimize latency.
		swapChainDesc.Scaling = mScaling;
		swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; // All Windows Store apps must use this SwapEffect.
		swapChainDesc.Flags = 0;

		ComPtr<IDXGIFactory2> dxgiFactory;
		HR2Ex(Context->Adapter->mAdapter->GetParent(__uuidof(IDXGIFactory2), &dxgiFactory));

		if (panel != nullptr)
		{
			// Create the swap chain and then associate it with the SwapChainBackgroundPanel.
			HR2Ex(
				dxgiFactory->CreateSwapChainForComposition(
					Context->m_d3dDevice.Get(),
					&swapChainDesc,
					nullptr,    // allow on all displays
					&swapChain
					)
				);

			// set the swap chain on the SwapChainBackgroundPanel
			ComPtr<ISwapChainBackgroundPanelNative> dxRootPanelAsNative;
			reinterpret_cast<IUnknown*>(panel)->QueryInterface(__uuidof(ISwapChainBackgroundPanelNative), (void**)&dxRootPanelAsNative);
			HR2Ex(dxRootPanelAsNative->SetSwapChain(swapChain.Get()));
		}
		else
		{
			HR2Ex(dxgiFactory->CreateSwapChainForCoreWindow(
					Context->m_d3dDevice.Get(),
					reinterpret_cast<IUnknown*>(window),
					&swapChainDesc,
					nullptr, // Allow on all displays.
					&swapChain
					)
				);
		}

		// Ensure that DXGI does not queue more than one frame at a time. This both reduces latency and
		// ensures that the application will only render after each VSync, minimizing power consumption.
		ComPtr<IDXGIDevice1>  dxgiDevice;
		HR2Ex(Context->m_d3dDevice.As(&dxgiDevice));
		HR2Ex(dxgiDevice->SetMaximumFrameLatency(1));
	}
	
	// Set the proper orientation for the swap chain, and generate the
	// 3D matrix transformation for rendering to the rotated swap chain.
	HR2Ex(swapChain->SetRotation(GetRotationMode(Orientation)));

	// Create a render target view of the swap chain back buffer.
	ComPtr<ID3D11Texture2D> target;
	HR2Ex(swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), &target));

	ntarget = ref new NativeRenderTarget(target);
}
Texture2D^ DXTargetSwapPanelOrWindow::Snapshot()
{
	if (!swapChain || !Context)
		return nullptr;

	// can't return the current one, maybe because of back buffer / buffer swapping?
	ComPtr<ID3D11Texture2D> backBuffer;
	HR2Ex(swapChain->GetBuffer(1, __uuidof(ID3D11Texture2D), &backBuffer));

	auto result = ref new Texture2D();
	result->Context = Context;
	result->InitCopy(backBuffer);
	return result;
}

DXTargetImageSource::DXTargetImageSource(int w, int h)
{
	Init(ref new SurfaceImageSource(w, h), w, h);
}
DXTargetImageSource::DXTargetImageSource(int w, int h, bool isOpaque)
{
	Init(ref new SurfaceImageSource(w, h, isOpaque), w, h);
}
DXTargetImageSource::DXTargetImageSource(SurfaceImageSource^ image, int w, int h)
{
	if (!image)
		throw ref new NullReferenceException();
	Init(image, w, h);
}
void DXTargetImageSource::Init(SurfaceImageSource^ image, int w, int h)
{
	watcher.Target = this;
	if (w <= 0 || h <= 0)
		throw ref new InvalidArgumentException();
	this->image = image;
	this->width = w;
	this->height = h;
	HR2Ex(reinterpret_cast<IUnknown*>(image)->QueryInterface(IID_PPV_ARGS(&sysimg)));
	Orientation = DisplayOrientations::Landscape;
	dpi = DPI::InchToDIP;
}
void DXTargetImageSource::Context::set(DXContext^ value)
{
	if (value == Context)
		return;
	if (value && Context && Context->Target == this)
		throw ExHelper::CreateException(ErrorCodes::TargetIsInUse);
	watcher.Context = value;
}
void DXTargetImageSource::RenderFinished()
{
	if (!ntarget)
		return;
	HR2Ex(sysimg->EndDraw());
	ntarget = nullptr;
}
void DXTargetImageSource::LoadDXData()
{
	if (ntarget)
		return;
	if (!Context)
		throw ExHelper::CreateException(ErrorCodes::ContextNotSet);

	int countcreate  = 0;
start:;
	countcreate++;

	// Express target area as a native RECT type.
	RECT updateRectNative; 
	updateRectNative.left = 0;
	updateRectNative.top = 0;
	updateRectNative.right = width;
	updateRectNative.bottom = height;

	ComPtr<IDXGISurface> dxgimage;
    ComPtr<IDXGIDevice> dxgiDevice;
    HR2Ex(Context->m_d3dDevice.As(&dxgiDevice));
	HR2Ex(sysimg->SetDevice(dxgiDevice.Get()));
	HRESULT beginDrawHR = sysimg->BeginDraw(updateRectNative, &dxgimage, &mvoffset);
	if (beginDrawHR == DXGI_ERROR_DEVICE_REMOVED || beginDrawHR == DXGI_ERROR_DEVICE_RESET)
	{
		// If the device has been removed or reset, attempt to recreate it and continue drawing.
		if(countcreate == 1)
		{
			ClearDXData();
			Context->CreateDeviceResources();
			goto start;
		}
		HR2Ex(beginDrawHR);
	}
	else
	{
		// Notify the caller by throwing an exception if any other error was encountered.
		HR2Ex(beginDrawHR);
	}
	ComPtr<ID3D11Texture2D> target;
	dxgimage.As(&target);

	ntarget = ref new NativeRenderTarget(target);
}
Texture2D^ DXTargetImageSource::Snapshot()
{
	if (!Context)
		return nullptr;

	// doesn't work correctly.. :/
	//throw ref new Platform::NotImplementedException();

	POINT offset;
	RECT updateRectNative; 
	updateRectNative.left = 0;
	updateRectNative.top = 0;
	updateRectNative.right = width;
	updateRectNative.bottom = height;

	ComPtr<IDXGISurface> dxgimage;
    ComPtr<IDXGIDevice> dxgiDevice;
	HR2Ex(Context->m_d3dDevice.As(&dxgiDevice));
	HR2Ex(sysimg->SetDevice(dxgiDevice.Get()));
	HR2Ex(sysimg->BeginDraw(updateRectNative, &dxgimage, &offset));

	// QI for target texture from DXGI surface.
	ComPtr<ID3D11Texture2D> d3DTexture;
	dxgimage.As(&d3DTexture);

	auto result = ref new Texture2D();
	result->InitCopy(d3DTexture);
	HR2Ex(sysimg->EndDraw());

	return result;
}


